/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ir, native, HomeCheckIrHint } from 'qvog-dsl';
import { ViewTreeTool } from '../../../utils/checker/ViewTreeTool';

const SEVERITY = 2;
const DOC_PATH = 'docs/image-sync-load-check-dsl.md';
const DESCRIPTION = 'Asynchronous loading is recommended when a large image is input.';
let viewTreeTool: ViewTreeTool = new ViewTreeTool();
const IMAGE: string = 'Image';
const SYNCLOAD: string = 'syncLoad';
const results: ir.Stmt[] = [];
const visited = new Map<string, Set<number>>();

export default native<HomeCheckIrHint>({ use: 'ir' })()
    .match((stmt): stmt is ir.Stmt => {
        const file = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getFilePath();
        const line = stmt.getCfg().getDeclaringMethod().getLine();
        if (!line) {
            return false;
        }
        if (!visited.has(file)) {
            visited.set(file, new Set());
            visited.get(file)?.add(line);
            return true;
        }
        const set = visited.get(file);
        if (!set?.has(line)) {
            set?.add(line);
            return true;
        }
        return false;
    })
    .when((stmt) => {
        results.length = 0;
        const method = stmt.getCfg().getDeclaringMethod();
        const clazz = method.getDeclaringArkClass();
        obtainClassViewTree(clazz, results);
        obtainMethodViewTree(method, results);

        return true;
    })
    .report((_, { api }): void => {
        const lineSet = new Set<number>();
        for (let stmt of results) {
            const line = stmt.getOriginPositionInfo().getLineNo()
            if (lineSet.has(line)) {
                continue;
            }
            lineSet.add(line);
            const warnInfo = getLineAndColumn(stmt);
            if (!warnInfo) {
                continue;
            }
            api.report({
                location: {
                    file: warnInfo.filePath,
                    range: {
                        start: {
                            line: warnInfo.lineNum,
                            column: warnInfo.startCol
                        },
                        end: {
                            line: warnInfo.lineNum,
                            column: warnInfo.endCol
                        },
                    }
                },
                severity: SEVERITY,
                description: DESCRIPTION,
                docPath: DOC_PATH,
            })
        }
    });
function obtainClassViewTree(clazz: ir.ArkClass, results: ir.Stmt[]): void {
    let viewTreeRoot = clazz.getViewTree()?.getRoot();
    if (!viewTreeRoot) {
        return;
    }
    traverseViewTree(viewTreeRoot, results);
}

function obtainMethodViewTree(method: ir.ArkMethod, results: ir.Stmt[]): void {
    let viewTreeRoot = method.getViewTree()?.getRoot();
    if (!viewTreeRoot) {
        return;
    }
    traverseViewTree(viewTreeRoot, results);
}

function traverseViewTree(viewTreeRoot: ir.ViewTreeNode, results: ir.Stmt[]): void {
    if (viewTreeTool.hasTraverse(viewTreeRoot)) {
        return;
    }
    if (viewTreeRoot.name === IMAGE) {
        for (let [key, vals] of viewTreeRoot.attributes) {
            if (key !== SYNCLOAD) {
                continue;
            }
            let stmt = checkSyncLoadByVals(vals);
            if (stmt) {
                results.push(stmt);
            }
        }
    }
    if (viewTreeRoot.children.length > 0) {
        for (let child of viewTreeRoot.children) {
            traverseViewTree(child, results);
        }
    }
}

function checkSyncLoadByVals(vals: [ir.Stmt, (ir.Constant | ir.ArkInstanceFieldRef | ir.MethodSignature)[]]): ir.Stmt | null {
    let stmt: ir.Stmt | null = null;
    for (let val of vals) {
        if (val instanceof ir.ArkInvokeStmt) {
            stmt = val;
        } else if (val instanceof ir.ArkAssignStmt) {
            let leftOp = val.getLeftOp();
            if (leftOp instanceof ir.Local) {
                stmt = leftOp.getDeclaringStmt();
            }
        } else if (val instanceof Array) {
            let value = val[0].toString();
            if (value === 'true') {
                return stmt;
            }
        }
    }
    return null;
}

function getLineAndColumn(stmt: ir.Stmt): {
    lineNum: number;
    startCol: number;
    endCol: number;
    filePath: string;
} | undefined {
    const originPosition = stmt.getOriginPositionInfo();
    const line = originPosition.getLineNo();
    const arkFile = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
    if (arkFile) {
        let text = stmt.getOriginalText();
        let startCol = 0;
        if (!text || text?.length === 0) {
            return undefined;
        }
        let checkText = '.syncLoad(true)';
        let originalTexts = text.split('\n');
        let lineCount = -1;
        for (let originalText of originalTexts) {
            lineCount++;
            if (!originalText.includes(checkText)) {
                continue;
            }
            if (lineCount === 0) {
                startCol = originalText.indexOf(checkText) + originPosition.getColNo();
            } else {
                startCol = originalText.indexOf(checkText) + 1;
            }
            let endCol = startCol + checkText.length - 1;
            let lineNum = line + lineCount;
            const originPath = arkFile.getFilePath();
            return { lineNum, startCol, endCol, filePath: originPath };
        }
    }
    return { lineNum: -1, startCol: -1, endCol: -1, filePath: '' };
}